# -*- coding;utf-8 -*-
"""
File name : WriteQuestionList2Docx.py.PY
Program IDE : PyCharm
Create file time: 2024/4/27 9:36
File Create By Author : 朱清闯
"""
import re
from html.parser import HTMLParser
from docx import Document
from docx.shared import Inches
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.shared import Pt
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from datetime import datetime
import os
from django.conf import settings
from utils.uploadfile import get_new_name
import utils.LatexToOxml as latex_2_omxl
from utils import RemoveAllFile


# 提取<p></p>便签内的文本
def extract_p_tag_content_and_positions(html_string):
    # 编写正则表达式，匹配<p>标签内的内容以及标签的开始和结束位置
    pattern = r'<p>(.*?)</p>'
    # 使用re.search()查找匹配项
    match = re.search(pattern, html_string, re.DOTALL)
    if match:
        # 提取标签内的内容
        content = match.group(1)
        # 返回内容以及开始和结束位置
        return content
    else:
        # 如果没有找到匹配项，返回None
        return None, None, None


# 提取img标签内的src部分，并且只取/static开头部分
def extract_image_src(html_content):
    # 使用正则表达式查找<img>标签中的src属性
    src_match = re.search(r'<img\s+[^>]*?src="([^"]+)"', html_content)
    if src_match:
        src = src_match.group(1)
        # print('src', src)
    else:
        return ''
    # 使用正则表达式匹配以static开头的子字符串
    static_match = re.search(r'static.*', src)
    # 如果找到了匹配项，则返回匹配到的子字符串
    if static_match:
        # print('static_match:', static_match.group())
        return static_match.group()
    else:
        # 如果没有找到以static开头的子字符串，则返回None或者一个空字符串
        return ''


# 分离标签p与标签table的嵌套
def fix_nested_html_tags_p_table(html_string):
    # 正则表达式来匹配<p>标签内的<table>标签的起始部分
    pattern_start = r'<p>(.*?)<table'
    # 正则表达式来匹配<table>标签的结束部分直到下一个</p>标签
    pattern_end = r'</table>(.*?)</p>'

    # 替换逻辑
    while True:
        # 寻找<p>标签内的<table>标签的起始部分
        match_start = re.search(pattern_start, html_string, re.DOTALL)
        if not match_start:
            break  # 如果没有找到，退出循环

        # 提取<p>标签内<table>标签之前的文本
        before_table = match_start.group(1)

        # 搜索<table>标签结束到下一个</p>标签的部分
        match_end = re.search(pattern_end, html_string[match_start.end():], re.DOTALL)
        if not match_end:
            break  # 如果没有找到，退出循环

        # 提取<table>标签之后的文本直到下一个</p>标签
        after_table = match_end.group(1)

        # 构建修复后的字符串片段
        fixed_fragment = f'<p>{before_table}<table{match_end.group(0)}</table><p>{after_table}</p>'

        # 替换原始字符串中的对应部分
        html_string = html_string[:match_start.start()] + fixed_fragment + html_string[
                                                                           match_start.start() + match_start.end() + len(
                                                                               match_end.group(0)):]

    return html_string


# 寻找第一个<p></p>内的第一个img
def find_first_img_index(html_string):
    # 匹配<img ...>标签的正则表达式，其中...表示任意属性和内容
    img_pattern = r'<img[^>]+>'
    # 查找第一个匹配项
    match = re.search(img_pattern, html_string)
    # 如果找到匹配项，返回开始和结束索引
    if match:
        start_index = match.start()
        end_index = match.end() - 1  # 因为re.search返回的是包含结束'>'的索引，所以减1
        return start_index, end_index
    else:
        # 如果没有找到，返回None或适当的错误信息
        return -1, -1


# 分离<p>内多个<img>标签的嵌套
def separate_img_from_p_tags(html_string):
    result_array = []
    # 如果不包含<img>标签，则直接返回原字符串
    if '<img' not in html_string:
        return html_string
    result_array_temp = html_string.split('</p>')
    for item in result_array_temp:
        # 如果空字符串或者标签内为空格
        if item == '' or re.search(r'<p>\s*</p>', item + '</p>'):
            continue
        if item[:3] == '<p>':
            item = item + '</p>'
        # 原本非嵌套关系
        elif item[:4] == '<img':
            p_index = item.find('<p>')
            if p_index > 0:
                result_array.append(item[:p_index])
                result_array.append(item[p_index:] + '</p>')
            else:
                result_array.append(item)
            continue
        # 标签p内没有<img标签的嵌套
        if '<img' not in item:
            result_array.append(item)
            continue
        # 标签p内有<img标签的嵌套
        else:
            while True:
                match_start, match_end = find_first_img_index(item)
                # <img标签之前没有内容
                if match_start == 3:
                    result_array.append(item[match_start:match_end + 1])
                    # 删除<img>部分内容，继续查找，即去<img之前的内容和结束后的内容
                    item = item[:match_start] + item[match_end + 1:]
                # <img标签之前有内容，则把<img之前的内容和<img里面的内容分别取过来
                elif match_start > 3:
                    result_array.append(item[:match_start] + '</p>')
                    result_array.append(item[match_start:match_end + 1])
                    # 删除<img>部分内容，并添加<p>后继续查找
                    item = '<p>' + item[match_end+1:]
                elif match_start == -1:
                    break
            # 处理剩余最后一个<p></p>
            if re.search(r'<p>\s*</p>', item):
                continue
            else:
                result_array.append(item)
    result = ''.join(result_array)
    return result


# 分离p标签内的table标签的嵌套
def separate_tables_from_p_tags(html_string):
    result_array = []
    if '<table' not in html_string:
        return html_string
    result_array_temp = html_string.split('</p>')
    for item in result_array_temp:
        # 如果空字符串或者标签内为空格
        if item == '' or re.search(r'<p>\s*</p>', item + '</p>'):
            continue
        # p标签开头的，存在两种情况
        if item[:3] == '<p>':
            table_index_start = item.find('<table')
            # 没有嵌套关系，即只有单个<p>标签
            if table_index_start < 0:
                result_array.append(item + '</p>')
            # p标签内嵌套<table>
            else:
                while True:
                    table_index_end = item.find('</table>')
                    if table_index_start == 3:
                        result_array.append(item[table_index_start:table_index_end + 1])
                        item = item[:table_index_start] + item[table_index_end + 8:]
                    elif table_index_start > 3:
                        result_array.append(item[:table_index_start] + '</p>')
                        result_array.append(item[table_index_start: table_index_end + 8])
                        item = '<p>' + item[table_index_end + 8:]
                    elif table_index_start == -1:
                        if len(item) > 7:
                            result_array.append(item)
                        break
                    table_index_start = item.find('<table')
        # 原本非嵌套关系，table标签尾部还有部分p标签内容
        elif item[:6] == '<table':
            p_index = item.find('<p>')
            if p_index > 0:
                result_array.append(item[:p_index])
                result_array.append(item[p_index:] + '</p>')
            else:
                result_array.append(item)
            continue
    result = ''.join(result_array)
    return result


# 从html表格中提取内容，返回二维数组
class TableParser(HTMLParser):
    def __init__(self):
        super().__init__()
        self.in_table = False
        self.in_tr = False
        self.in_td = False
        self.rows = []
        self.current_row = []
        self.current_data = ''

    def handle_starttag(self, tag, attrs):
        if tag == 'table':
            self.in_table = True
        elif tag == 'tr' and self.in_table:
            self.in_tr = True
            # 开始新的行时，如果当前行有数据，则将其添加到rows中
            if self.current_row:
                self.rows.append(self.current_row)
                self.current_row = []
        elif (tag == 'td') and self.in_tr:
            self.in_td = True
        elif (tag == 'th') and self.in_tr:
            self.in_td = True

    def handle_endtag(self, tag):
        if tag == 'table':
            self.in_table = False
        elif tag == 'tr':
            self.in_tr = False
            # 结束行时，将当前行添加到rows中
            if self.current_row:
                self.rows.append(self.current_row)
                self.current_row = []
        elif tag == 'td':
            self.in_td = False
            # 结束单元格时，将当前数据添加到当前行中
            if self.current_data.strip():
                self.current_row.append(self.current_data.strip())
            self.current_data = ''
        elif tag == 'th':
            self.in_td = False
            # 结束单元格时，将当前数据添加到当前行中
            if self.current_data.strip():
                self.current_row.append(self.current_data.strip())
            self.current_data = ''

    def handle_data(self, data):
        if self.in_td:
            self.current_data += data


def extract_table_data(html):
    parser = TableParser()
    parser.feed(html)
    return parser.rows


# 找到第一个标签，返回其内容和开始、结束索引号
def find_first_html_tag(html_string):
    # 匹配第一个HTML标签（包括自闭合标签）
    match = re.search(r'<(\w+)(.*?)>', html_string)
    if match:
        # 提取标签名称
        tag_name = match.group(1)
        # 开始位置是匹配到的整个标签的开始位置
        start_index = match.start()
        # 结束位置需要区分是单标签还是成对标签
        end_tag = f'</{tag_name}>'
        if end_tag in html_string:
            # 查找结束标签
            end_index = html_string.find(end_tag, start_index)
            if end_index != -1:
                end_index += len(end_tag) - 1  # 减去1是因为我们要'>'的索引
            else:
                # 如果没有找到对应的结束标签，则默认结束位置为字符串末尾
                end_index = len(html_string)
        else:
            # 对于自闭合标签，结束位置就是'>'的位置
            end_index = match.end() - 1
        return tag_name, start_index, end_index
    else:
        return None, None, None  # 如果没有找到标签，则返回None


# 先分离p与img标签的嵌套、p与table的标签嵌套
# 按照前后顺序，把各标签统计到数组中，如[{'type': 'p', 'content': '以下说法正确的是x'}, {'type': 'img', 'content': '/static'}]
def sorting_marked(content_str):
    marked_position = []
    content_str = separate_img_from_p_tags(content_str)
    content_str = separate_tables_from_p_tags(content_str)
    # print('separate_p_tags:', content_str)
    while len(content_str) > 0:
        result, start_pos, end_pos = find_first_html_tag(content_str)
        # print('找到第一个tag的内容', content_str[start_pos:end_pos + 1])
        # print('找到第一个tag的开始位置', start_pos)
        # print('找到第一个tag的结束位置', end_pos)
        if result and len(result) > 0:
            first_html_content = content_str[start_pos:end_pos + 1]
            if result == 'p':
                result_content = extract_p_tag_content_and_positions(first_html_content)
                marked_position.append({'type': 'p', 'content': result_content})
            elif result == 'img':
                result_content = extract_image_src(first_html_content)
                marked_position.append(({'type': 'img', 'content': result_content}))
            elif result == 'table':
                result_content = extract_table_data(first_html_content)
                marked_position.append({'type': 'table', 'content': result_content})
            else:
                break
            content_str = content_str[end_pos + 1:]
        else:
            break
    return marked_position


# 参数content_str为一段标签字符串，可能混合<p><img><table>等标签，进行各标签按顺序分离成数组，每个标签单独一个元素；
# 如果p标签包含latex公式，即$$包围的字符，则另外添加特殊处理
def write_str_into_docx(document, content_str, question_number=0):
    print('before write string into docx')
    marked_array = sorting_marked(content_str)
    print('marked_array:', marked_array)
    # 添加题号
    if marked_array[0]['type'] == 'p' and question_number > 0:
        marked_array[0]['content'] = str(question_number) + '.' + marked_array[0]['content']
    print('loop structure, document add operate!')
    for item in marked_array:
        if item['type'] == 'p' and item['content'] != '':
            # 如果不包含latex公式
            if not latex_2_omxl.exist_latex(item['content']):
                document.add_paragraph(text=item['content'])
            # 如果包含latex公式，则需要转换后再写入docx文件中
            else:
                print('before deal with latex in content')
                # 去掉所有item_content中的
                item_content = item['content']
                print('item_content:', item_content)
                if '$$' in item_content:
                    print('has $$')
                    item_content = item_content.replace('$$', '')
                if '$' in item_content:
                    print('has $')
                    item_content = item_content.replace('$', '')
                # while r'\$\$' in item_content:
                #     print('before item_content in while loop!', item_content)
                #     item_content = item_content.replace(r'\$\$', '')
                #     print('item_content in while loop!', item_content)
                print('delete $$:', item_content)
                item_content_latex = latex_2_omxl.latex_to_oxml(item_content)
                print('item_content_latex', item_content_latex)
                print('math string:', latex_2_omxl.mathml2str(item_content_latex))
                # print('item_content_latex element:', item_content_latex['element'])
                paragraph = document.add_paragraph()
                paragraph_format = paragraph.paragraph_format
                paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
                run = paragraph.add_run('')
                # document.add_paragraph(text=item_content_latex)
                run._element.append(item_content_latex)
                # run.text = item_content_latex
                print('deal with latex in content over!')
        elif item['type'] == 'img':
            document.add_picture(item['content'])
        elif item['type'] == 'table':
            table_data = item['content']
            print('table_data:', table_data)
            # 添加表格
            table = document.add_table(rows=len(table_data), cols=len(table_data[0]))
            hdr_cells = table.rows[0].cells
            for j in range(len(table_data[0])):
                hdr_cells[j].text = str(table_data[0][j])
                hdr_cells[j].width = Inches(1.0)  # 可以调整列宽

            for i in range(1, len(table_data)):
                row_cells = table.rows[i].cells
                for j in range(len(table_data[i])):
                    row_cells[j].text = str(table_data[i][j])

            # 设置表格对齐方式
            table.alignment = WD_TABLE_ALIGNMENT.CENTER


# 把html符号转换成实际符号
def convert_html_tags_to_symbol(content_str):
    pattern_list = [(r'&lt;', '<'),
                    (r'&gt;', '>'),
                    (r'&amp;', '&'),
                    (r'&quot;', '"'),
                    (r'&times;', '×'),
                    (r'&divide;', '÷')]
    for item in pattern_list:
        content_str = re.sub(item[0], item[1], content_str)
    return content_str


# 读取题目列表，写入到document中
def write_question_list_into_docx(paper_information=None, question_list=None):
    try:
        # print('starting write all questions into docx now!')
        if question_list is None:
            return None
        # 如果该文件夹下的文件超过10个，则删除掉文件
        if RemoveAllFile.file_count(settings.FILE_ROOT) > 10:
            RemoveAllFile.remove_all_file(settings.FILE_ROOT)
        docx_path = os.path.join(settings.FILE_ROOT, 'templates/paper.docx')
        document = Document(docx_path)
        if paper_information is None:
            paper_information = {'title': datetime.now(), 'answer_position': 'attach', 'type_sorted': False}
        answer_position = paper_information['answer_position']
        type_sorted = paper_information['type_sorted']
        paper_title = paper_information["title"]
        document.add_heading(paper_title, level=1)
        # print('add title:', paper_title)
        if answer_position == 'attach':
            # print('select attach!')
            if type_sorted:
                # 排序分组选择题和填空题
                question_list.sort(key=lambda x: x["type"])
            for index_question in range(len(question_list)):
                content_question = question_list[index_question]['content']
                content_question = convert_html_tags_to_symbol(content_question)
                answer_question = question_list[index_question]['answer']
                answer_question = convert_html_tags_to_symbol(answer_question)
                answer_question = '<p>【答案】</p>' + answer_question
                explain_question = question_list[index_question]['explain']
                explain_question = convert_html_tags_to_symbol(explain_question)
                explain_question = '<p>【解析】</p>' + explain_question
                write_str_into_docx(document, content_question, index_question + 1)
                # print('write content over!')
                write_str_into_docx(document, answer_question)
                # print('write answer over!')
                write_str_into_docx(document, explain_question)
                # print('write explain over!')
        else:
            # print('select detach!')
            if type_sorted:
                # 排序分组选择题和填空题
                question_list.sort(key=lambda x: x["type"])
            for index_question in range(len(question_list)):
                content_question = question_list[index_question]['content']
                # print('before convert html!')
                content_question = convert_html_tags_to_symbol(content_question)
                # print('convert html over!')
                write_str_into_docx(document, content_question, index_question + 1)
                # print('write content over!')
            document.add_page_break()
            # 题目结束后，添加答案和解析的标题，并居中
            document_answer_title = '答案和解析'
            document.add_heading(document_answer_title)
            # print('add answer title!')
            for index_question in range(len(question_list)):
                answer_question = question_list[index_question]['answer']
                answer_question = convert_html_tags_to_symbol(answer_question)
                answer_question = '<p>【答案】</p>' + answer_question
                explain_question = question_list[index_question]['explain']
                explain_question = convert_html_tags_to_symbol(explain_question)
                explain_question = '<p>【解析】</p>' + explain_question
                document.add_paragraph(f'【第{index_question+1}题】')
                write_str_into_docx(document, answer_question)
                # print('write answer over!')
                write_str_into_docx(document, explain_question)
                # print('write explain over!')
        # print('everything is ok, return url!')
        path = os.path.join(settings.FILE_ROOT, paper_title)
        file_name = get_new_name() + '.docx'
        full_path = path + file_name
        document.save(full_path)
        docx_url_return = '/static/files/' + paper_title + file_name
        return docx_url_return
    except Exception as e:
        print('Exception:', e)
        return None
